#include "aerial\Game\GameScene\BattleScene.h"

// fly object
#include "aerial\Game\GameObject\FlyObject\Fighter.h"
#include "aerial\Game\GameObject\FlyObject\Scout.h"
#include "aerial\Game\GameObject\FlyObject\Commander.h"

#include "aerial\Object\Panel.h"
#include "aerial\Object\Text.h"
#include "aerial\Object\Button.h"

// equipment
#include "aerial\Game\GameObject\Equipment\Weapon.h"
#include "aerial\Game\GameObject\Equipment\Equipment.h"

#include "aerial\Core\NetMessage.h"

#include "aerial\Game\GameScene\MainMenuScene.h"
#include "aerial\Scene\Event.h"
#include "aerial\Scene\SceneManager.h"

// co-ordinate transformation
#include "aerial\Game\Helper\BattleHelper.h"
// contact processing
#include "aerial\Game\Helper\AirContactListener.h"

#include "Box2D\Box2D.h"
#include <iostream>

using namespace std;

const uchar BattleScene::MaxPlayerCount = 10;
const int BattleScene::MaxFullUpdate = 30;

BattleScene::BattleScene(): world(0), panelDisplayed(false), serverConnect(true), performed(false)
{
	memset(fObjs, 0, sizeof(fObjs));
	alivePlayerCount[0] = alivePlayerCount[1] = 0;
	playerCount = 0;
	blue = red = 0;
}

BattleScene::~BattleScene()
{
	netIO->A_TerminateThread();
	netIO->reset();
	netIO->CloseConnection();
	GameObject::SetWorld(0);
	delete world;
	delete inputEvent;
}

bool BattleScene::initialize()
{
	if (netIO->IsServer()) netIO->A_StartThread();
	BattleHelper::setRatio(50.f);
	BattleHelper::setMapHW(800.f, 600.f);
	// configure physics engine
	b2Vec2 gravity(0.f, 0.f);
	world = new b2World(gravity);
	world->SetContactListener(&airContactListener);
	// wall
	b2ChainShape chain;
	b2Vec2 vs[4];
	vs[0] = BattleHelper::getBox2dCoordinateFromHge(b2Vec2(0.f, 0.f));
	vs[1] = BattleHelper::getBox2dCoordinateFromHge(b2Vec2(3840.f, 0.f));
	vs[2] = BattleHelper::getBox2dCoordinateFromHge(b2Vec2(3840.f, 1080.f));
	vs[3] = BattleHelper::getBox2dCoordinateFromHge(b2Vec2(0.f, 1080.f));
	chain.CreateLoop(vs, 4);
	// create body
	b2BodyDef bodyDef;
	bodyDef.type = b2_staticBody;
	b2Body* wall = world->CreateBody(&bodyDef);
	wall->CreateFixture(&chain, 0.f);
	
	Equipment::SetEnv(hge, hgeResMan);

	GameObject::SetWorld(world);
	inputEvent = new hgeInputEvent();
	FlyObject::SetInputEvent(inputEvent);
	FlyObject::SetBattleScene(this);
	Weapon::InitializeCounter();
	background = hgeResMan->GetSprite("spr.bg");
	minibg = hgeResMan->GetSprite("spr.minimap");
	blue = hgeResMan->GetSprite("spr.blue");
	red = hgeResMan->GetSprite("spr.red");

	fullUpdateCount = 0;
	PlayerDescriptor* pds = netIO->GetPlayerInfo();
	playerCount = netIO->GetPlayerCount();

	toExit = false;
	winSide = 0;
	gradient.r = gradient.g = gradient.b = 1.f;
	gradient.a = 0.f;
	white = new hgeSprite(0, 0, 0, 800.f, 600.f);
	white->SetColor(gradient.GetHWColor());

	hint = new Text(hgeResMan, 5, 300, 100, 30);
	hint->SetText("");
	hint->addToGui(sceneManager);
	hintBox = new Panel(hgeResMan, 200, -110, 350, 110);
	panelHint = new Text(hgeResMan, 100, 30);
	panelHint->SetText("Exit to MainMenu?");
	hintBox->placeGuiObjectOffset( panelHint, 20, 30 );
	hintBox->placeGuiObjectOffset(
		exit = new Button(hgeResMan, 0.f, "Exit", 
			new SEvent<BattleScene>(this, &BattleScene::ExitToMainMenu)
		), 150, 70);

	hintBox->placeGuiObjectOffset(
		back = new Button(hgeResMan, 0.f, "Continue",
			new SEvent<BattleScene>(this, &BattleScene::Back)
		), 260, 70);
	hintBox->addToGui(sceneManager);

	int id = netIO->getMeId();
	uchar meSide = pds[id].side;

	for ( int i = 0; i < MAX_PLAYER_COUNT; i++ )
	{
		if (pds[i].available) {
			switch (pds[i].flyObjectType)
			{
			case TYPE_SCOUT:
				fObjs[i] = new Scout( i, pds[i].userName, 800.f, (i + 1) * 250.f, pds[i].side);
				break;
			case TYPE_FIGHTER:
				fObjs[i] = new Fighter( i, pds[i].userName, 800.f, (i + 1) * 250.f, pds[i].side);
				break;
			case TYPE_COMMANDER:
				fObjs[i] = new Commander( i, pds[i].userName, 800.f, (i + 1) * 250.f, pds[i].side);
				break;
			}
			fObjs[i]->SetMeSide(meSide);
			++alivePlayerCount[pds[i].side];
			sceneManager->addObject(fObjs[i]);
		}
	}
	self = fObjs[id];

	return true;
}

bool BattleScene::update(float dt)
{
	if (toExit) 
	{
		gradient.a += 0.02f;
		if (gradient.a >= 1.f && !performed) 
		{
			/// @brief record the setNextScene has already been performed
			performed = true;
			ExitToMainMenu();
		}
		return false;
	}
	if (serverConnect && !panelDisplayed && hge->Input_GetKeyState(HGEK_ESCAPE))
	{
		panelDisplayed = true;
		hintBox->SetTransfromationTween(200.f, 200.f, 0.25f);
	}
	// get elapsed time since engine start
	float ctime = hge->Timer_GetTime();

	// processing information from remote server
	Packet* p;
	for (int i = 0; i < 20; i++) {
		if (p = netIO->recv())
		{
			BitStream bsi(p->data, p->length, false);
			uchar whead;
			switch (p->data[0])
			{
			case ID_FOBJ_FULL:
				bsi.Read(whead);
				for ( int i = 0; i < MAX_PLAYER_COUNT; i++ )
				{
					if (fObjs[i])
					{
						fObjs[i]->DeSerializationFull(&bsi);
						fObjs[i]->RegulateDeviation();
					}
				}
				break;
			case ID_FOBJ_INCREMENT:
				{
					bsi.Read(whead);
					uchar pid;	// must be the same type as player->id
					bsi.Read(pid);
					if (pid < MaxPlayerCount && fObjs[pid] && fObjs[pid]->IsAvailable()) 
						fObjs[pid]->DeSerializationIncrement(&bsi);
				}
				break;
			case ID_ARMOR_UPDATE:
				{
					bsi.Read(whead);
					uchar pid;
					float armor;
					bsi.Read(pid);
					bsi.Read(armor);
					if (pid < MaxPlayerCount && fObjs[pid] && fObjs[pid]->IsAvailable()) fObjs[pid]->SetArmor(armor);
				}
				break;
			case ID_DISCONNECTION_NOTIFICATION: case ID_CONNECTION_LOST:
				if (netIO->IsServer()) 
				{
					uchar id = netIO->findPlayerBySystemAddress(p->systemAddress);
					BitStream bso;
					Exit ex;
					if (id == 255) break;
					ex.id = id;
					ex.Serialize(&bso);
					if ( fObjs[id] ) 
					{
						fObjs[id]->Die();
						hint->printf("%s exit.", netIO->GetPlayerInfo()[id].userName.C_String());
					}
					else cout << "unknown player id.\n";
					netIO->broadcastExcludePlayer(&bso, -1);
				}
				else
				{
					serverConnect = false;
					back->bEnabled = false;
					panelHint->SetText("Disconnect From Server.");
					// server disconnect force exit
					if (!panelDisplayed)
					{
						hintBox->SetTransfromationTween(200.f, 200.f, 0.25f);
					}
				}
				break;
			case ID_EXIT:
				{
					Exit ex;
					ex.Deserialize(&bsi);
					if ( ex.id >= 0 && ex.id < MAX_PLAYER_COUNT )
					{
						if ( fObjs[ex.id] ) 
						{
							fObjs[ex.id]->Die();
							hint->printf("%s exit.", netIO->GetPlayerInfo()[ex.id].userName.C_String());
						}
						else cout << "unknown player id.\n";
					}
				}
				break;
			default:
				cout << "Unavaliable user packet with head " << (int)p->data[0] << endl;
				break;
			}
			netIO->Deallocate(p);
		}
		else break;
	}
	
	// processing local input
	if (self->IsAvailable()) self->processInput();

	if (self->IsStateChange())
	{
		BitStream bso;
		bso.Write((uchar)ID_FOBJ_INCREMENT);
		self->SerializationIncrement(&bso);
		netIO->sendToBattleServer(&bso);
		self->SetStateChange(0);
	}

	// update gameobject status
	acc->Update(dt);

	// update physics engine by a fix frame
	world->Step(1.f / 60.f, 8, 3);
	
	CoordinateTransformBH();
	airContactListener.process(dt);
	 
	// push local status to remote server
	if (netIO->IsServer())
	{
		++fullUpdateCount;
		if (fullUpdateCount >= MaxFullUpdate)
		{
			BitStream bso;
			bso.Write((uchar)ID_FOBJ_FULL);
			for ( int i = 0; i < MAX_PLAYER_COUNT; i++ )
			{
				if (fObjs[i]) fObjs[i]->SerializationFull(&bso);
			}
			netIO->broadcastExcludePlayer(&bso, -1);
			fullUpdateCount = 0;
		}
	}
	
	if (alivePlayerCount[0] == 0 || alivePlayerCount[1] == 0) {
		if (alivePlayerCount[0]) {
			cout << "SIDE 1 wins.\n";
			winSide = 0;
		}
		else if (alivePlayerCount[1]) {
			cout << "SIDE 0 wins.\n";
			winSide = 1;
		}
		else {
			cout << "DRAW.\n";
			winSide = 2;
		}
		toExit = true;
	}
#ifdef _DEBUG
	assert(alivePlayerCount[0] >= 0);
	assert(alivePlayerCount[1] >= 0);
#endif
	return false;
}

void BattleScene::Back()
{
	if (serverConnect)
	{
		panelDisplayed = false;
		hintBox->SetTransfromationTween( 200, -110, 0.25 );
	}
}

void BattleScene::ExitToMainMenu()
{
	sceneManager->setNextScene(new MainMenuScene());
	Leave();
}

bool BattleScene::render(float dt)
{
	hge->Gfx_Clear(0);
	b2Vec2 T = self->GetPosition();
	float offsetX, offsetY;
	if (T.x < 400) offsetX = 400.f;
	else if (T.x < 3440) offsetX = T.x;
	else offsetX = 3440.f;
	if (T.y < 300) offsetY = 300.f;
	else if (T.y < 780) offsetY = T.y;
	else offsetY = 780.f;

	hge->Gfx_SetTransform(0.0f, 0.0f, 400 - offsetX, 300 - offsetY, 0.0f, 1.0f, 1.0f);
	background->Render(0, 0);
	acc->Render();
	hge->Gfx_SetTransform(0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f);
	fnt->SetColor(0xffffffff);
	fnt->printf(5, 5, HGETEXT_LEFT, "fps: %f", hge->Timer_GetDelta());
	fnt->printf(5, 55, HGETEXT_LEFT, "object: %d", acc->getObjectCount());
	fnt->printf(5, 105, HGETEXT_LEFT, "( %.3f, %.3f )", self->GetPosition().x, self->GetPosition().y);
	fnt->printf(5, 155, HGETEXT_LEFT, "ARMOR %d", (int)self->GetArmor());
	fnt->printf(5, 205, HGETEXT_LEFT, "POWER %d", (int)self->GetPower());
	renderMiniMap();
	gui->Render();
	if (toExit) {
		white->SetColor(gradient.GetHWColor());
		white->Render(0.f, 0.f);
		fnt->SetColor(0xff000000);
		fnt->SetScale(1.5);
		if (winSide == self->side) fnt->printf(400, 400, HGETEXT_MIDDLE, "You Win");
		else fnt->printf(300, 200, HGETEXT_MIDDLE, "You Lose");
		fnt->SetScale(1.0);
	}
	return false;
}

void BattleScene::CoordinateTransformBH()
{
	for (int i = 0; i < playerCount; i++) 
		if (fObjs[i]->IsAvailable()) fObjs[i]->CoordinateTransform();
}

void BattleScene::renderMiniMap()
{
	minibg->Render(0.f , 470.f);
	for (int i = 0; i < playerCount; i++)
	{
		b2Vec2 pos = fObjs[i]->GetPosition();
		b2Vec2 newPos( pos.x / 10.f, pos.y / 10.f + 490.f);
		if (fObjs[i]->side != fObjs[i]->MeSide) red->RenderEx(newPos.x, newPos.y, 0.f, 0.5f, 0.5f);
		else blue->RenderEx(newPos.x, newPos.y, 0.f, 0.5f, 0.5f);
	}
}